package domain.user;

import java.util.Set ;

import junit.framework.Assert ;
import junit.framework.TestCase ;
import persistence.CorruptDataException ;
import persistence.PersistenceController ;
import persistence.xstream.XStreamPersistenceFactory ;
import persistence.xstream.XmlDatabaseFileList ;
import presentation.UIUser ;
import util.DuplicationException ;
import util.HierarchicalSet ;
import util.NotFoundException ;
import domain.ForumData ;
import domain.searchEngine.origin.OriginSearchEngineFactory ;



/**
 * The Class UserControllerTest.
 */
public class UserControllerTest extends TestCase
{

	/** The forum. */
	private ForumData forum;

	/** The user control. */
	private UserController userControl;

	private OriginSearchEngineFactory search ;



	/**
	 * Test addAdministratorPrivileges
	 * @throws DuplicationException
	 * @throws NotFoundException
	 */
	public void testAddAdministratorPrivileges() throws DuplicationException, NotFoundException
	{
		Member admin = this.forum.getUsers().createMember("testAdmin", "1234", this.setPrivilegesForNewMember());
		admin.addPrivilege(Privilege.GIVE_PERMISSION);

		Member member1 = this.forum.getUsers().createMember("member1", "abcd", this.setPrivilegesForNewMember());
		Member member2 = this.forum.getUsers().createMember("member2", "blabla", this.setPrivilegesForNewMember());

		//Test1: add administrator privileges to unregistered user:
		boolean isNotFound = false;
		try{
			this.userControl.addAdministratorPrivileges(admin.getId(), "user1");
		}
		catch(NotFoundException e){
			isNotFound = true;
		}
		catch(UnpermittedActionException e){
			Assert.fail("UnpermittedActionException for permitted member");
		}
		Assert.assertTrue("unregistered user cannot become an admin", isNotFound);

		//Test2: add administrator privileges to registered user:
		try{
			this.userControl.addAdministratorPrivileges(admin.getId(), "member1");
		}
		catch(NotFoundException e){
			Assert.fail("NotFoundException for registered member");
		}
		catch(UnpermittedActionException e){
			Assert.fail("UnpermittedActionException for permitted member");
		}
		Assert.assertTrue("not all admin privileges were given", this.forum.getUsers().getMember(member1.getId()).isAllowed(Privilege.EDIT_ANY_POST));
		Assert.assertTrue("not all admin privileges were given", this.forum.getUsers().getMember(member1.getId()).isAllowed(Privilege.ADD_DIRECTORY));
		Assert.assertTrue("not all admin privileges were given", this.forum.getUsers().getMember(member1.getId()).isAllowed(Privilege.DELETE_THREAD));
		Assert.assertTrue("not all admin privileges were given", this.forum.getUsers().getMember(member1.getId()).isAllowed(Privilege.DELETE_POST));
		Assert.assertTrue("not all admin privileges were given", this.forum.getUsers().getMember(member1.getId()).isAllowed(Privilege.GIVE_PERMISSION));
		Assert.assertTrue("not all admin privileges were given", this.forum.getUsers().getMember(member1.getId()).isAllowed(Privilege.DENY_PERMISSION));

		//Test3: add administrator privileges not by admin:
		boolean isUnpermitted = false;
		try{
			this.userControl.addAdministratorPrivileges(member2.getId(), "member1");
		}
		catch(NotFoundException e){
			Assert.fail("NotFoundException for registered member");
		}
		catch(UnpermittedActionException e){
			isUnpermitted = true;
		}
		Assert.assertTrue("unpermitted user cannot give privileges", isUnpermitted);
	}

	/**
	 * Test addModeratorPrivileges
	 * @throws DuplicationException
	 * @throws NotFoundException
	 */
	public void testAddModeratorPrivileges() throws DuplicationException, NotFoundException
	{
		Member admin = this.forum.getUsers().createMember("testAdmin", "1234", this.setPrivilegesForNewMember());
		admin.addPrivilege(Privilege.GIVE_PERMISSION);

		Member member1 = this.forum.getUsers().createMember("member1", "abcd", this.setPrivilegesForNewMember());
		Member member2 = this.forum.getUsers().createMember("member2", "blabla", this.setPrivilegesForNewMember());

		//Test1: add moderator privileges to unregistered user:
		boolean isNotFound = false;
		try{
			this.userControl.addAdministratorPrivileges(admin.getId(), "user1");
		}
		catch(NotFoundException e){
			isNotFound = true;
		}
		catch(UnpermittedActionException e){
			Assert.fail("UnpermittedActionException for permitted member");
		}
		Assert.assertTrue("unregistered user cannot become a moderator", isNotFound);

		//Test2: add moderator privileges to registered user:
		try{
			this.userControl.addAdministratorPrivileges(admin.getId(), "member1");
		}
		catch(NotFoundException e){
			Assert.fail("NotFoundException for registered member");
		}
		catch(UnpermittedActionException e){
			Assert.fail("UnpermittedActionException for permitted member");
		}
		Assert.assertTrue("not all moderator privileges were given", this.forum.getUsers().getMember(member1.getId()).isAllowed(Privilege.EDIT_ANY_POST));
		Assert.assertTrue("not all moderator privileges were given", this.forum.getUsers().getMember(member1.getId()).isAllowed(Privilege.DELETE_THREAD));
		Assert.assertTrue("not all moderator privileges were given", this.forum.getUsers().getMember(member1.getId()).isAllowed(Privilege.DELETE_POST));

		//Test3: add moderator privileges not by admin:
		boolean isUnpermitted = false;
		try{
			this.userControl.addAdministratorPrivileges(member2.getId(), "member1");
		}
		catch(NotFoundException e){
			Assert.fail("NotFoundException for registered member");
		}
		catch(UnpermittedActionException e){
			isUnpermitted = true;
		}
		Assert.assertTrue("unpermitted user cannot give privileges", isUnpermitted);
	}

	/**
	 * Test login.
	 * Tests that the password of the member is indeed the password in the signature
	 * Also checks according to the id returned from login the username and his password
	 *
	 * @throws NotFoundException
	 * @throws BadPasswordException
	 * @throws DuplicationException
	 */
	public void testLogin() throws NotFoundException, BadPasswordException, DuplicationException
	{
		//Test1: login registered user
		final long userId = this.userControl.register("Hadas", "1234");
		final UIUser user1 = this.userControl.login("Hadas", "1234");
		final long uIdTest = user1.getId();
		Assert.assertEquals("user id should be the same as in registration", userId, uIdTest);
		Assert.assertEquals("user id should reteive the correct user from repository", "Hadas", ((Member)this.forum.getUsers().getUser(uIdTest)).getUsername());
		Assert.assertTrue("logged in user should be active", ((Member)user1).isActive());

		final UIUser user2 = this.userControl.login("Hadas", "1234");
		Assert.assertEquals("second login should give the same user", user1, user2);

		//Test2: login unregistered user
		boolean isFailed = false;
		try{
			this.userControl.login("Vera", "abc");
			Assert.fail("unregistered user cannot login!");
		}
		catch(NotFoundException e){
			isFailed = true;
		}
		Assert.assertTrue("unregistered user cannot login, NotFoundException is expected", isFailed);

	}


	/**
	 * test logout
	 * @throws DuplicationException
	 * @throws BadPasswordException
	 * @throws NotFoundException
	 */
	public void testLogout() throws DuplicationException, NotFoundException, BadPasswordException{

		//Test1: logout of logged in member:
		long user1Id = this.userControl.register("Hadas", "1234");
		this.userControl.login("Hadas", "1234");
		try
		{
			this.userControl.logout(user1Id);
		}
		catch (NotFoundException e)
		{
			Assert.fail("NotFoundException for registered member");
		}
		catch (UnpermittedActionException e)
		{
			Assert.fail("UnpermittedActionException for registered member");
		}
		Assert.assertFalse("logged out user was not registered as non active", this.forum.getUsers().getMember(user1Id).isActive());


		//Test2: logout of not logged in member:
		long user2Id = this.userControl.register("Vera", "abcd");
		try
		{
			this.userControl.logout(user2Id); //should succeed!!
			Assert.fail("logout allowed when not logged in");
		}
		catch (NotFoundException e)
		{
			Assert.fail("NotFoundException for registered member");
		}
		catch (UnpermittedActionException e)
		{
			// success
		}
		Assert.assertFalse("logged out user was not registered as non active", this.forum.getUsers().getMember(user2Id).isActive());


		//Test3: logout of unregistered user:
		long user3Id = this.userControl.registerGuest().getId();
		boolean isNotFound = false;
		try
		{
			this.userControl.logout(user3Id);
		}
		catch (NotFoundException e)
		{
			isNotFound = true;
		}
		catch (UnpermittedActionException e)
		{
			Assert.fail("UnpermittedActionException for registered member");
		}
		Assert.assertTrue("unregistered user cannot logout", isNotFound);

	}


	/**
	 * Test register.
	 * Tests whether after registration the username and encrypted password are in the member collection
	 * Also checks the only user id
	 *
	 * @throws DuplicationException
	 * @throws NotFoundException
	 * @throws BadPasswordException
	 */
	public void testRegister() throws DuplicationException, NotFoundException, BadPasswordException
	{
		//Test1:
		final long userId = this.userControl.register("Hadas", "1234");
		Assert.assertFalse("password is not encrypted", this.forum.getUsers().getMember("Hadas").getPassword().equals("1234"));
		Assert.assertEquals("new registered user details were not correctly saved", ((Member) this.forum.getUsers().getUser(userId)).getUsername(), "Hadas");

		//Test2:
		final long userId2 = this.userControl.register("Vera", "1234");
		Assert.assertFalse("similar password should not encrypted in the same way", this.forum.getUsers().getMember("Hadas").getPassword()
			.equals(this.forum.getUsers().getMember("Vera").getPassword()));
		Assert.assertTrue("ids should be different for two different users", userId2 != userId);
		Assert.assertEquals("new registered user details were not correctly saved", ((Member) this.forum.getUsers().getUser(userId2)).getUsername(), "Vera");

		//Test3:
		boolean isDuplicated = false;
		try{
			this.userControl.register("Hadas", "5678");
			Assert.fail("username must be unique");
		}
		catch(DuplicationException e){
			isDuplicated = true;
		}
		Assert.assertTrue("username must be unique, DuplicationException is expected", isDuplicated);
		Assert.assertEquals("the duplicated registration should not change the previous registration", userId, this.userControl.login("Hadas", "1234").getId());
	}

	/**
	 * test registerGuest
	 * @throws NotFoundException
	 */
	public void testRegisterGuest() throws NotFoundException{
		UIUser user1 = this.userControl.registerGuest();
		Assert.assertEquals("registered guest should be in repository", user1, this.forum.getUsers().getUser(user1.getId()));
	}

	/**
	 * test unregisterGuest
	 */
	public void testUnregisterGuest(){
		UIUser user1 = this.userControl.registerGuest();
		this.userControl.unregisterGuest(user1.getId());
		boolean isNotFound = false;
		try{
			this.forum.getUsers().getUser(user1.getId());
		}
		catch(NotFoundException e){
			isNotFound = true;
		}
		Assert.assertTrue("unregistered guest should not be in repository", isNotFound);

	}

	/**
	 * test updatePassword
	 * @throws DuplicationException
	 */
	public void testUpdatePassword() throws DuplicationException{
		UIUser guest1 = this.userControl.registerGuest();
		long member1Id = this.userControl.register("Hadas", "1234");

		//Test1: update password with wrong inserted old password:
		boolean isBadPassword = false;
		try
		{
			this.userControl.updatePassword(member1Id, "abcd", "blabla");
		}
		catch (NotFoundException e)
		{
			Assert.fail("NotFoundException for registered member");
		}
		catch (BadPasswordException e)
		{
			isBadPassword = true;
		}
		Assert.assertTrue("wrong old password should result BadPasswordException", isBadPassword);

		//Test2: update password for unregistered user (a guest):
		boolean isNotFound = false;
		try
		{
			this.userControl.updatePassword(guest1.getId(), "1234", "abcd");
		}
		catch (NotFoundException e)
		{
			isNotFound = true;
		}
		catch (BadPasswordException e)
		{
			Assert.fail("BadPasswordException for correct password");
		}
		Assert.assertTrue("unregistered member should result NotFoundException", isNotFound);

		//Test3: update password with registered guest and correct old password:
		try
		{
			this.userControl.updatePassword(member1Id, "1234", "abcd"); //operation should succeed
		}
		catch (NotFoundException e)
		{
			Assert.fail("NotFoundException for registered member");
		}
		catch (BadPasswordException e)
		{
			Assert.fail("BadPasswordException for correct password");
		}
		try
		{
			this.userControl.login("Hadas", "abcd"); //login with new password should succeed
		}
		catch (NotFoundException e)
		{
			Assert.fail("NotFoundException for registered member");
		}
		catch (BadPasswordException e)
		{
			Assert.fail("BadPasswordException for correct password");
		}

	}

	/**
	 * set-up for the UserControllerTest
	 * @throws CorruptDataException
	 * @see junit.framework.TestCase#setUp()
	 */
	@Override
	protected void setUp() throws CorruptDataException
	{
		XmlDatabaseFileList list = this.getFileList() ;
		XStreamPersistenceFactory builder = new XStreamPersistenceFactory(PersistenceController.BUILD_NEW, list) ;
		this.forum = new ForumData(builder);
		this.search = new OriginSearchEngineFactory("index.xml", this.forum) ;
		this.userControl = new UserController(this.forum, this.search.getIndexAgent());
	}

	private XmlDatabaseFileList getFileList()
	{
		XmlDatabaseFileList list = new XmlDatabaseFileList() ;
		list.setMemberFile("users.xml") ;
		list.setDirectoryFile("dirs.xml") ;
		list.setThreadFile("threads.xml") ;
		list.setPostFile("posts.xml") ;
		list.setLoginFile("logins.xml") ;
		return list ;
	}

	/**
	 * Sets the privileges for new member.
	 *
	 * @return set< privilege>
	 */
	private Set<Privilege> setPrivilegesForNewMember()
	{
		final Set<Privilege> privileges = new HierarchicalSet<Privilege>(
			Privilege.VIEW_POST,
			Privilege.ADD_POST,
			Privilege.ADD_THREAD,
			Privilege.EDIT_POST,
			Privilege.REPLY_POST
		) ;

		return privileges ;
	}


}
